﻿//#define XAML_TRACE
#if XAML_TRACE
using _obj_writer = agree.xaml_obj_writer_debug;
#else
using _obj_writer = agree.xaml_obj_writer;
#endif

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.IO;
using System.Reactive.Concurrency;
using System.Windows.Markup;
using System.Xaml;
using System.Xaml.Schema;
using System.Threading;

using alib;
using alib.Concurrency;
using alib.Debugging;

using agree.schema;

namespace agree.composition
{
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public class f_xaml_load : f_primitive<String, XamlLoadInstance>
	{
		public f_xaml_load(IRtParent parent, Schema sch)
			: base(parent)
		{
			this.sch = sch ?? parent.FindSchema();
			if (sch == null)
				throw new Exception("No schema");
		}
		public f_xaml_load(IRtParent parent)
			: this(parent, default(Schema))
		{
		}

		readonly Schema sch;

		protected override XamlLoadInstance func(String filename)
		{
			return new XamlLoadInstance(this, filename, sch);
		}
	};
}

namespace agree
{
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public sealed class XamlLoadInstance : m_singleton<String, IIdentity>, ρρFilename, ρρSchema, ρρDefaultDirectories
	{
		public XamlLoadInstance(IRtParent parent, String filename, Schema sch)
			: base(parent, filename)
		{
			this.ns = new namescope_helper();
			this.dps = new DirectoryPathSet(this);
			this.Schema = sch ?? parent.FindSchema();
		}
		public XamlLoadInstance(IRtParent parent, String filename)
			: this(parent, filename, default(Schema))
		{
		}
		public XamlLoadInstance(IRtParent parent)
			: this(parent, default(String))
		{
		}

		///////////////////////////////////////////////////////////////////////
		///
		public String Filename
		{
			get { return t0; }
			set
			{
				if (t0 != null)
					throw new ArgumentException("'Filename' is write-once");
				if (value == null)
					return;
				t0 = value;
			}
		}
		/// 
		///////////////////////////////////////////////////////////////////////


		///////////////////////////////////////////////////////////////////////
		///
		Schema sch;
		public Schema Schema
		{
			get { return sch; }
			set { sch = value; }
		}
		/// 
		///////////////////////////////////////////////////////////////////////


		///////////////////////////////////////////////////////////////////////
		///
		namescope_helper ns;
		public override INameScope NameScope { get { return ns; } }
		/// 
		///////////////////////////////////////////////////////////////////////


		///////////////////////////////////////////////////////////////////////
		///
		DirectoryPathSet dps;
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		public DirectoryPathSet DefaultDirectories
		{
			get { return dps; }
			set
			{
				if (value == null || value.Count == 0)
					return;
				if (dps.Count > 0)
					throw not.impl;
				dps = value;
			}
		}
		/// 
		///////////////////////////////////////////////////////////////////////


		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// 
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		protected override void start(String t)
		{
			Debug.Print("XamlLoadInstance start '{0}'", t0);

			TaskControl.Scheduler.Schedule<XamlLoadInstance>(this, (sch, _this) => _this.setup_transform());
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// 
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public void RootCreated(IIdentity item)
		{
			this.root = item;

			PP.Schema.SetValue(item, sch);

			if (item.GetType().Name == "StringCorpus")	// todo: fix fix see also monad.cs
			{
				if (this.Name != null)
				{
					PP.Name.SetValue(item, this.Name);
					parent.NameScope.UnregisterName(this.Name);
					parent.NameScope.RegisterName(this.Name, item);
				}
			}

			_set_root(item);
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// 
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		IIdentity root;
		public IIdentity RootElement
		{
			get
			{
				/// full synchronous wait for load completion, since we don't know how this RootElement might
				/// be used. (In practice, this property is accessed from XAML)
				Wait();

				return root;
			}
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// 
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		IDisposable setup_transform()
		{
			var fi = DirectoryPathSet.FindFileInfo(parent, t0);
			if (fi == null || !fi.Exists)
				throw new FileNotFoundException("Xaml file not found", t0);

			if (this.Schema == null)
				throw new ArgumentNullException("Schema");

			xaml_to_obj_transform(fi);

			_set_complete();

			Debug.Print("XamlLoadInstance finished '{0}'", t0);

			return EmptyDisposable.Instance;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// 
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		void xaml_to_obj_transform(FileInfo _use_fi)
		{
			using (var xow = new _obj_writer(this))
			using (var xxr = new xaml_xml_reader(_use_fi.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite), this))
			{
				try
				{
					xxr.transform(xow);
				}
				catch (XamlObjectWriterException xex)
				{
					var ex = xex.InnerException ?? xex;

					throw new XamlLoadException(ex, "\nError in XAML file '{0}' at line {1}, column {2}:\nException: {3}\nMessage: {4}",
						Filename,
						xxr.LineNumber,
						xxr.LinePosition,
						ex.GetType().Name,
						ex.Message);
				}
			}
		}
	};
}
